/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.iec.edp.caf.lock.config;

import io.iec.caf.data.jpa.repository.config.EnableCafJpaRepositories;
import io.iec.edp.caf.lock.extension.jpa.JpaLockExtensionImpl;
import io.iec.edp.caf.lock.service.api.api.DistributedLockFactory;
import io.iec.edp.caf.lock.service.api.api.LockExtension;
import io.iec.edp.caf.lock.service.api.api.LockService;
import io.iec.edp.caf.lock.service.api.repositories.BatchLockRepo;
import io.iec.edp.caf.lock.service.api.repositories.LockRepo;
import io.iec.edp.caf.lock.service.memory.MemoryLockFactory;
import io.iec.edp.caf.lock.service.memory.MemoryLockFactoryImpl;
import io.iec.edp.caf.lock.service.redis.DistributedLockFactoryImpl;
import io.iec.edp.caf.lock.service.redis.DistributedLockService;
import io.iec.edp.caf.lock.service.redis.DistributedLockServiceImpl;
import io.iec.edp.caf.lock.service.redis.DistributedLocksFactory;
import io.iec.edp.caf.lockservice.api.IDistributedLockFactory;
import io.iec.edp.caf.lockservice.api.ILockService;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.Resource;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @author wangyandong
 * @date 2019/8/30 10:54
 * @email wangyandong@inspur.com
 */
@Configuration(proxyBeanMethods = false)
@EntityScan({"io.iec.edp.caf.lockservice.api"})
@EnableConfigurationProperties({
        RedisLockProperties.class
})
@EnableCafJpaRepositories(basePackages = {"io.iec.edp.caf.lock.service.api.repositories","io.iec.edp.caf.lockservice.core.repositories"})
public class LockServiceAutoConfiguration {
    @Autowired
    private ApplicationContext ctx;
    static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";

    @Bean
    @ConditionalOnExpression("#{!'false'.equals(environment['redis.enabled'])}")
    public DistributedLockFactory getStandardDistributedLockFactory(RedissonClient redisson,RedisLockProperties redisLockProperties) {
        Config config =getRedissonConfig(redisson,redisLockProperties);
        return new DistributedLockFactoryImpl(config);
    }

    @Bean
    @ConditionalOnExpression("#{'false'.equals(environment['redis.enabled'])}")
    public DistributedLockFactory getMemDistributedLockFactory() {
        return new MemoryLockFactoryImpl();
    }


    @Bean
    public LockExtension getLockExtension(LockRepo repo, BatchLockRepo batchRepo){
        return new JpaLockExtensionImpl(repo,batchRepo);
    }
    @Bean
    //声明单例还是原型
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public LockService getLockService(DistributedLockFactory factory,LockExtension lockExtension) {
        return new DistributedLockServiceImpl(factory, lockExtension);
    }


    //////////////////////////////////////////////以下是兼容老的bean///////////////////////////////////////////////////////
    @Bean
    // @ConditionalOnMissingBean
    @ConditionalOnExpression("#{!'false'.equals(environment['redis.enabled'])}")
    public IDistributedLockFactory getOldStandardDistributedLockFactory(RedissonClient redisson, RedisLockProperties redisLockProperties) {
        Config config = getRedissonConfig(redisson,redisLockProperties);
        return new DistributedLocksFactory(config);
    }

    @Bean
    @ConditionalOnExpression("#{'false'.equals(environment['redis.enabled'])}")
    public IDistributedLockFactory getOldMemDistributedLockFactory() {
        return new MemoryLockFactory();
    }

    @Bean
    //声明单例还是原型i
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public ILockService getOldLockService(DistributedLockFactory factory, LockExtension lockExtension) {
        return new DistributedLockService(factory,lockExtension);
    }

    private InputStream getConfigStream(String config) throws IOException {
        Resource resource = this.ctx.getResource(config);
        return resource.getInputStream();
    }
    private String[] convert(List<String> nodesObject) {
        List<String> nodes = new ArrayList(nodesObject.size());
        Iterator var3 = nodesObject.iterator();

        while(true) {
            while(var3.hasNext()) {
                String node = (String)var3.next();
                if (!node.startsWith("redis://") && !node.startsWith("rediss://")) {
                    nodes.add("redis://" + node);
                } else {
                    nodes.add(node);
                }
            }
            return (String[])nodes.toArray(new String[nodes.size()]);
        }
    }

    private  Config getRedissonConfig(RedissonClient redisson,RedisLockProperties redisLockProperties){
        Config config = null;
        if(!redisLockProperties.isEnableAlone()){
            config=redisson.getConfig();
        }else{
            //redisson-spring-boot-starter-3.11.2版本的starter
            Method clusterMethod = ReflectionUtils.findMethod(RedisLockSetting.class, "getCluster");
            Method timeoutMethod = ReflectionUtils.findMethod(RedisLockSetting.class, "getTimeout");
            Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, redisLockProperties.getRedis());
            int timeout;
            Method nodesMethod;
            if (null == timeoutValue) {
                timeout = 0;
            } else if (!(timeoutValue instanceof Integer)) {
                nodesMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");
                timeout = ((Long)ReflectionUtils.invokeMethod(nodesMethod, timeoutValue)).intValue();
            } else {
                timeout = (Integer)timeoutValue;
            }

            if (redisLockProperties.getRedis().getRedisson()!=null&&redisLockProperties.getRedis().getRedisson().getConfig() != null) {
                try {
                    InputStream is = this.getConfigStream(redisLockProperties.getRedis().getRedisson().getConfig());
                    config = Config.fromJSON(is);
                } catch (IOException var11) {
                    try {
                        InputStream is = this.getConfigStream(redisLockProperties.getRedis().getRedisson().getConfig());
                        config = Config.fromYAML(is);
                    } catch (IOException var10) {
                        throw new IllegalArgumentException("Can't parse config", var10);
                    }
                }
            } else if (redisLockProperties.getRedis().getSentinel() != null) {
                nodesMethod = ReflectionUtils.findMethod(RedisLockSetting.Sentinel.class, "getNodes");
                Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, redisLockProperties.getRedis().getSentinel());
                String[] nodes;
                if (nodesValue instanceof String) {
                    nodes = this.convert(Arrays.asList(((String)nodesValue).split(",")));
                } else {
                    nodes = this.convert((List)nodesValue);
                }

                config = new Config();
                ((SentinelServersConfig)config.useSentinelServers().setMasterName(redisLockProperties.getRedis().getSentinel().getMaster()).addSentinelAddress(nodes).setDatabase(redisLockProperties.getRedis().getDatabase()).setConnectTimeout(timeout)).setPassword(redisLockProperties.getRedis().getPassword());
            } else {
                Method method;
                if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, redisLockProperties.getRedis()) != null) {
                    Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, redisLockProperties.getRedis());
                    method = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
                    List<String> nodesObject = (List) ReflectionUtils.invokeMethod(method, clusterObject);
                    String[] nodes = this.convert(nodesObject);
                    config = new Config();
                    ((ClusterServersConfig)config.useClusterServers().addNodeAddress(nodes).setConnectTimeout(timeout)).setPassword(redisLockProperties.getRedis().getPassword());
                } else {
                    config = new Config();
                    String prefix = "redis://";
                    method = ReflectionUtils.findMethod(RedisLockProperties.class, "isSsl");
                    if (method != null && (Boolean) ReflectionUtils.invokeMethod(method, redisLockProperties)) {
                        prefix = "rediss://";
                    }

                    ((SingleServerConfig)config.useSingleServer().setAddress(prefix + redisLockProperties.getRedis().getHost() + ":" + redisLockProperties.getRedis().getPort()).setConnectTimeout(timeout)).setDatabase(redisLockProperties.getRedis().getDatabase()).setPassword(redisLockProperties.getRedis().getPassword());
                }
            }
        }
        return config;
    }

}
